import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import javax.naming.*;
import java.rmi.*;
import java.rmi.server.*;

class Lock{
   final public ReentrantLock x_lock;
   final public Condition condition;
    public boolean koniec;
    Lock(){
      koniec=false;
      x_lock = new ReentrantLock();
      condition = x_lock.newCondition();
    }
    
    public void open(){
      x_lock.lock();
      condition.signal();
      x_lock.unlock();
    }
  
}

class LevelChars{       //elementy na planszy
  static char empty='_';
  static char snake='O';
  static char head='@';
  static char food='x';
}

class Level {
 
  private char level[][];       //plansza
  private int x;        //wymiary planszy
  private int y;
  private int foodAmmount;      //ilosc jedzenie ktora powinna byc na planszy
  public int[] head;            //wspolrzedne glowy
  private Random rand;

  Level(int x, int y, int food){
    level = new char[x][];
    for(int i = 0; i < x; i++){  
      level[i] = new char[y];
    }

    this.x=x;
    this.y=y;
    foodAmmount=food;
    head = new int[2];
    newGame();
    rand = new Random();
  }

  void newGame(){       //centrowanie węża i czyszczenie planszy
    head[0]=x/2;
    head[1]=y/2;
    clearLevel();
  }

  void clearLevel(){    //zapełniania planszy pustymi polami
    for(int i=0; i<x; i++){
      for(int j=0; j<y; j++){
        level[i][j]=LevelChars.empty;
      }
    }
   
  }

  void generateFood(){  //tworzy zadaną wcześniej ilość jedzenia na planszy
    int[] temp=new int[2];
    for(int i=0; i<foodAmmount; i++){
      do{
        temp[0]=rand.nextInt(x);
        temp[1]=rand.nextInt(y);
      }while(level[temp[0]][temp[1]] != LevelChars.empty);

      setFood(temp[0], temp[1]);
    }
   
  }

 
  int[][] getFood(){    //zwraca jedzenie w tablicy
    
    List<int[]> out = new ArrayList<int[]>();
    for(int i=0; i<x; i++){
      for(int j=0; j<y; j++){
        if(level[i][j]==LevelChars.food){
	  int food[] = new int[2];
	  food[0]=i;
	  food[1]=j;
	  out.add(food);
        }
      }
    }
    int[][] a=new int[out.size()][2];
    a=out.toArray(a);
    return a;
   
  }

  void setFood(int xf, int yf){ //dodanie jedzenia na zadanej pozycji
    level[xf][yf]=LevelChars.food;
   
  }

  void eatFood(int xf, int yf){ //zjadanie jedzenia i wygenerowanie nowego w innym miejscu
   
    level[xf][yf]=LevelChars.empty;
    System.out.println("zjadlem:" + xf+ " " + yf);
    int[] temp=new int[2];
    do{
      temp[0]=rand.nextInt(x);
      temp[1]=rand.nextInt(y);
    }while(level[temp[0]][temp[1]] != LevelChars.empty || ( temp[0]==xf && temp[1]==yf ));
   
    System.out.println("dodalem:" + temp[0]+ " " + temp[1]);
    setFood(temp[0], temp[1]);
   
  }
 
  void setSnake(int xf, int yf){
   
    level[xf][yf]=LevelChars.snake;
   
  }

  int setHead(int xh, int yh){
    System.out.println(xh+ " " + yh);
    if(xh<0 || xh>=x || yh<0 || yh>=y)  //głowa poza planszą
      return -1;
   
    if(level[xh][yh]==LevelChars.snake){        //głowa wejdzie w węża
      return -1;
    }
   
    head[0]=xh;
    head[1]=yh;
   
    if(level[xh][yh]==LevelChars.food){ //głowa wejdzie na jedzenie
      eatFood(xh,yh);
      return 1;
    }
   
    return 0;   //głowa na pustym polu
   
  }


  void clearSnake(int xs, int ys){      //usuwanie węża z danego pola
   
    if(xs>=0 && xs<x && ys>=0 && ys<y)
        level[xs][ys]=LevelChars.empty;
   
  }

  String print(){       //zwraca planszę w stringu
    String out = new String();
   
    for(int i=0; i<x; i++){
      for(int j=0; j<y; j++){
        if(i==head[0] && j==head[1]){
          out+="@|";
        } else {
          out+=level[i][j] + "|";
        }
      }
        out+="\n";
    }
    return out;
  }

}

class Snake{
  private LinkedList<int[]> snake;      //lista ze wspolrzednymi weza
  private int direction;        //kierunek w ktorym porusza sie waz
  private Iterator it;
  private Level level;
  private int head[];   //wspolrzedne glowy
  private int dieStep;  //ilosc korkow po ktorych waz skraca sie o 1 element
  private int step;     //ilosc wykonanych krokow
 
  Snake(int x, int y, Level pl, int step){
    snake = new LinkedList<int[]>();
    it = snake.iterator();
    level = pl;
    head=new int[2];
    head[0]=x;
    head[1]=y;
    direction=1;
    dieStep = step;
    this.step = 0;
    add(x,y);
    add(x-1,y);
    add(x-2,y);
    setSnake();
    level.generateFood();
  }

  void add(int x, int y){       //dodaje kawałek weża
    int[] tab = new int[2];
    tab[0]=x;
    tab[1]=y;
    snake.add(tab);
  }

  void addFirst(int x, int y){  //dodaje kawałek węża na początek
    int[] tab = new int[2];
    tab[0]=x;
    tab[1]=y;
    snake.addFirst(tab);
  }

  void setSnake(){      //wpisuje węża na planszę
    it = snake.iterator();
    int[] head=(int[])it.next();
    int[] tab;
    level.setSnake(head[0],head[1]);

    while(it.hasNext())
    {
          tab=(int[])it.next();
          level.setSnake(tab[0],tab[1]);
    }

    level.setHead(head[0],head[1]);
   
  }

  int[][] getSnake(){
    
    int[][] out=new int[snake.size()][2];
    out=snake.toArray(out);
    return out;
    
  }

  void clearSnake(){    //czyści węża z planszy
    it = snake.iterator();
    int[] tab;
   
    while(it.hasNext())
    {
          tab=(int[])it.next();
          level.clearSnake(tab[0],tab[1]);
    }
  }

  void removeTail(){    //czyści ostatni element węża na planszy
    int[] tab;
    tab=snake.getLast();
    level.clearSnake(tab[0],tab[1]);

  }

  int moveSnake(int turn){     //przesunięcie węża w zadanym kierunku
    int score=0;

    switch(turn){
          case 0:	//up
            head[1]++;
            break;
          case 1:	//right
            head[0]++;
            break;
          case 2:	//down
            head[1]--;
            break;
          case 3:	//left
            head[0]--;
            break;
        }

    int action=level.setHead(head[0], head[1]); //przesuwamy głowę i zwracana jest akcja w zależności gdzie znajdzie się głowa
    //usunWeza();
    addFirst(head[0], head[1]); //przesuwamy głowę o jedną pozycję
    step++;
   
    if(action==1){      //wąż zjada i rośnie o 1, czyli nie usuwamy ostatniego elementu
      step=0;
      score=1;
    }

    if(action==0){      //wąż porusza się doprzodu, więc trzeba usunąć ostatni element
      removeTail();
      snake.removeLast();
    }

    if(step==dieStep){  //wąż maleje z głodu
      step=0;
      removeTail();
      snake.removeLast();

      if(snake.size() <= 0){    //jeśli zniknie cały to ginie
        action=-1;
      }
    }

    if(action==-1){     //wąż ginie
      score=-2;
      return score;
    }
   
    level.setSnake(head[0],head[1]);    //dpoisuje glowe na planszy
    return score;
  }

  void newGame(){
    clearSnake();
      snake.clear();
      level.newGame();
      head[0]=level.head[0];
      head[1]=level.head[1];
      direction = 1;
      step=0;
      add(head[0],head[1]);
      add(head[0]-1,head[1]);
      add(head[0]-2,head[1]);
      setSnake();
      level.generateFood();
  }
 
 
}


class Mover {
  private LinkedList<int[]> scoreBoard;
  boolean firstPlayer;
  int direction;
  Level level;
  Snake snake;
  int food;
  int interval;
  int dieStep;
  int x;
  int y;
  int timeLimit;
  Lock l;
  boolean endGame;
  int repeats;
  int globalScore;
 
   public Mover(int x, int y, int food, int dieStep, int interval, int timeLimit, int repeats, Lock l) throws RemoteException{

    super();
    this.l=l;
    setConfig(x, y, food, dieStep, interval, timeLimit, repeats);
    //newGame();
    firstPlayer=true;
    scoreBoard = new LinkedList<int[]>();
    endGame=true;
  }

public void PrintList(List<int[]> list)
	{
		for (int[] sElem : list)
		{
			System.out.print(" [" + sElem[0] + " " + sElem[1] + "]");
		}	
		System.out.println();
	}

  public void Start() {
    int score=0;
    globalScore=0;
    int tempScore=0;
    l.x_lock.lock();
    int player=1;
    while(!l.koniec){
	try{
	  l.condition.await();
	}catch ( InterruptedException ie ) {}
	endGame=false;
	globalScore=0;
      for(int j=0; j<repeats; j++){
	for(int i=0; i<timeLimit; i+=interval){    //test węża chodzącego losowo
	  PrintList(Arrays.asList(level.getFood()));
	  System.out.println(level.print());
	  
	  try{
	    Thread.sleep(interval);
	  } catch( Exception ex){}
	
	  tempScore=snake.moveSnake(direction);
	  score+=tempScore;
	  if(tempScore<0){
	    //System.out.println(score);
	    globalScore+=score;
	    score=0;
	    snake.newGame();
	    //direction=0;
	    break;
	  }
	  //direction=0;
	}
      }
      globalScore+=score;
      //System.out.println(globalScore);
      //addScore(player, globalScore);
      
      //player++;
      endGame=true;
      System.out.println("--------------------------------------------------------");
      newGame();
    }
    l.x_lock.unlock();

  }

   public void goUp() throws RemoteException{
    direction=0;
  }
  
   public void goRight() throws RemoteException{
    direction=1;
  }
 
   public void goDown() throws RemoteException{
    direction=2;
  }

   public void goLeft() throws RemoteException{
    direction=3;
  }

   public int[][] getFood() throws RemoteException{
    return level.getFood();
  }

   public int[][] getSnake() throws RemoteException{
    return snake.getSnake();
  }

   public int[] getSceneDim() throws RemoteException{
    int[] tab = new int[2];
    tab[0]=x;
    tab[1]=y;
    return tab;
  }

  void addScore(int num, int score){
    if(firstPlayer){
      firstPlayer=false;
      int[] tab = new int[2];
      tab[0]=num;
      tab[1]=score;
      scoreBoard.addFirst(tab);
    } else {
      int[] tab = new int[2];
      tab[0]=num;
      tab[1]=score;
      boolean setLast=false;
      //scoreBoard.add(0,tab);

      Iterator it = scoreBoard.iterator();
      int[] elem=(int[])it.next();
      int i=0;
      while(elem[1]>tab[1] && !setLast)
      {
	    if(it.hasNext()){
	      elem=(int[])it.next();
	    } else {
	      setLast=true;
	    }
      }
      
      if(!setLast){
	scoreBoard.add(scoreBoard.indexOf(elem),tab);
      } else {
	scoreBoard.addLast(tab);
      }
      }
    
  }

  void getScoreBoard(){
    int[][] out=new int[scoreBoard.size()][2];
    out=scoreBoard.toArray(out);
    for(int i=0; i < scoreBoard.size(); i++)
      System.out.println(out[i][0] + " " + out[i][1]);
  }

  void setConfig(int x, int y, int food, int dieStep, int interval,int timeLimit, int repeats){
    this.dieStep=dieStep;
    this.food=food;
    this.x=x;
    this.y=y;
    this.interval=interval;
    this.timeLimit=timeLimit;
    this.repeats=repeats;
    newGame();
  }

  void newGame(){
    level = new Level(x, y, food);
    snake = new Snake(x/2,y/2, level, dieStep);
    direction=0;
    snake.setSnake();
    snake.newGame();
  }

  int getScore(){
    return globalScore;
  }

  String getLevel(){
    return level.print();
  }

  boolean getGameState(){
    return endGame;
    
  }	

}

class Klient extends UnicastRemoteObject implements KlientI {
  private Mover mover;

  Klient(Mover move) throws RemoteException{
    mover=move;
  }

  public void goUp() throws RemoteException{
    mover.goUp();
  }
  
  public void goRight() throws RemoteException{
    mover.goRight();
  }
  
  public void goLeft() throws RemoteException{
    mover.goLeft();
  }
  
  public void goDown() throws RemoteException{
    mover.goDown();
  }
  

  public int[][] getFood() throws RemoteException{
    return mover.getFood();
  }

  public int[][] getSnake() throws RemoteException{
    return mover.getSnake();
  }

  public int[] getSceneDim() throws RemoteException{
    return mover.getSceneDim();
  }

  public boolean kill() throws RemoteException{
    return mover.getGameState();
  }

}

class Controler extends UnicastRemoteObject implements ZarzadcaI {
  private Mover mover;
  private Lock l;

  Controler(Mover move, Lock l) throws RemoteException{
    mover=move;
    this.l=l;
  }

  public boolean getGameState() throws RemoteException{
    return mover.getGameState();
  }
  
  public String getLevel() throws RemoteException{
    return mover.getLevel();
  }

  public void startGame() throws RemoteException{
    l.open();
  }

  public int getScore() throws RemoteException{
    return mover.getScore();
  }


  public void setConfig(int x, int y, int food, int dieStep, int interval, int timeLimit, int repeats) throws RemoteException{
    mover.setConfig(x, y, food, dieStep, interval, timeLimit, repeats);
  }
}

class Serwer {
 
  public static void main(String[] args) {
    /*Lock l = new Lock();
    Mover move=new Mover(10, 10, 5, 5, 500, 10000, l);
    ( new Thread(move)).start();
    Klient klient=new Klient(move);
    ( new Thread(klient)).start();
    l.open();
    try{
        Thread.sleep( 11000);
      } catch( Exception ex){}
    Klient klient2=new Klient(move);
    ( new Thread(klient2)).start();
    move.getScoreBoard();
    l.open();
    try{
        Thread.sleep( 11000);
      } catch( Exception ex){}
    Klient klient3=new Klient(move);
    ( new Thread(klient3)).start();
    move.getScoreBoard();
    l.open();
    try{
        Thread.sleep( 11000);
      } catch( Exception ex){}
    move.getScoreBoard();
    Klient klient4=new Klient(move);
    ( new Thread(klient4)).start();
    move.getScoreBoard();
    l.open();
    try{
        Thread.sleep( 13000);
      } catch( Exception ex){}
    move.getScoreBoard();*/
    try{
    Lock l = new Lock();
    Mover move = new Mover(10, 10, 5, 5, 500, 40000, 10, l);
	
	Controler zarzadca = new Controler(move, l);
	Naming.rebind("Za", zarzadca);

	Klient klient = new Klient(move);
        Naming.rebind ("Mover", klient);

        System.out.println ("Service bound....");
	move.Start();
    } catch(Exception e){System.out.println(e.toString());}
  }
}
